﻿using System.ComponentModel;
using Dyao.Book.Application.Abstractions;
using Dyao.Book.Contract;
using Dyao.Book.Entity;
using Dyao.Book.Repository.Abstractions;
using Foundation.Core;
using Foundation.Spider;
using Foundation.Spider.DataFlow;
using Foundation.Spider.DataFlow.Parser;
using Foundation.Spider.Extensions;
using Foundation.Spider.Http;
using Foundation.Spider.Selector;
using Foundation.Uow;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Dyao.Book.Application
{
    [DisplayName("MatchBookChapterSource")]
    public class MatchBookChapterSourceSpider : Spider, IMatchBookChapterSourceSpider
    {
        private readonly ISpiderManager _spiderMgr;

        private readonly IJsonSerializer _serializer;

        private readonly DependenceServices _dependenceServices;

        public MatchBookChapterSourceSpider(IOptions<SpiderOptions> options,
        DependenceServices services,
        ILogger<MatchBookChapterSourceSpider> logger,
        ISpiderManager spiderMgr,
        IJsonSerializer serializer,
        DependenceServices dependenceServices) : base(options, services, logger)
        {
            _spiderMgr = spiderMgr;
            _serializer = serializer;
            _dependenceServices = dependenceServices;
        }

        /// <inheritdoc cref="IMatchBookChapterSourceSpider.CrawBookChapterSourceUrlAsync"/>
        public async Task<bool> CrawBookChapterSourceUrlAsync(BookSourceDto args, BookSourceWarehouseDto setting, CancellationToken cancellationToken)
        {
            var request = BuildRequest(args, setting);
            await ExecuteAsync(cancellationToken, () =>
            {
                AddRequestsAsync(request);
            });

            return true;
        }

        /// <summary>
        /// 构建请求独享
        /// </summary>
        /// <param name="args"></param>
        /// <param name="setting"></param>
        /// <returns></returns>
        public Request BuildRequest(BookSourceDto args, BookSourceWarehouseDto setting)
        {
            var request = new Request(args.BookUrl)
            {
                // 请求超时10秒
                Timeout = 10000
            };

            if (!setting.Header.IsNullOrEmpty())
            {
                var header = _serializer.Deserialize<BookSourceWarehouseHeaderDto>(setting.Header);
                if (!header.UserAgent.IsNullOrEmpty())
                    request.Headers.UserAgent = header.UserAgent;

                if (!header.Cookies.IsNullOrEmpty())
                    request.Headers.Cookie = header.Cookies;
            }

            request.Properties.Add(QiDianConstant.BookChapterRequestProp, args);
            request.Properties.Add(QiDianConstant.BookChapterTargetTypeRequestProp, setting);

            return request;
        }

        protected override async Task InitializeAsync(CancellationToken stoppingToken = default)
        {
            // 添加自定义解析
            AddDataFlow<MatchBookChapterSourceDataParser>();
            AddDataFlow<ConsoleStorage>();

            // 错误信息
            OnRequestError += (request, response) =>
            {
                var bookProp = (BookSourceDto)request.Properties.GetOrDefault(QiDianConstant.BookChapterRequestProp);
                string message = $"Uri:{request.RequestUri}, ReasonPhrase:{response.ReasonPhrase}";
                _spiderMgr.PushMessageAsync(bookProp.BookId, OperationTypeEnum.BookChapterSource, message);
            };

            // 超时请求
            OnRequestTimeout += (requests) =>
            {
                foreach (var request in requests)
                {
                    var bookProp = (BookSourceDto)request.Properties.GetOrDefault(QiDianConstant.BookChapterRequestProp);
                    string message = $"Uri:{request.RequestUri} RequestTimeout";
                    _spiderMgr.PushMessageAsync(bookProp.BookId, OperationTypeEnum.BookChapterSource, message);
                }
            };
            await Task.CompletedTask;
        }


        public class MatchBookChapterSourceDataParser : DataParser
        {
            public override Task InitializeAsync()
            {
                return Task.CompletedTask;
            }

            protected override async Task ParseAsync(DataFlowContext context)
            {
                var logger = context.ServiceProvider.GetService<ILogger<MatchBookChapterSourceDataParser>>();
                var bookProp = (BookSourceDto)context.Request.Properties.GetOrDefault(QiDianConstant.BookChapterRequestProp);
                var settingProp = (BookSourceWarehouseDto)context.Request.Properties.GetOrDefault(QiDianConstant.BookChapterTargetTypeRequestProp);
                var serializer = context.ServiceProvider.GetService<IJsonSerializer>();
                var spiderRule = serializer.Deserialize<BookSourceWarehouseSpiderRuleDto>(settingProp.SpiderRule); // 规则
                var chapterRule = spiderRule.BookChapterSpiderRule;
                var spiderMgr = context.ServiceProvider.GetService<ISpiderManager>();
                try
                {
                    // 网页数据解析
                    var masterNodes = context.Selectable.SelectList(Selectors.XPath(chapterRule.ChapterList));
                    int index = 0;
                    foreach (var masterNode in masterNodes)
                    {
                        if (index == 0 && spiderRule.SkipFirstLine)
                        {
                            index++;
                            continue;
                        }
                        var items = masterNode.SelectList(Selectors.XPath(chapterRule.ChapterItem));
                        var sourceEntities = new List<MatchChapterSourceDto>();
                        foreach (var item in items)
                        {
                            var name = item.Select(Selectors.XPath(chapterRule.ChapterName))?.Value;
                            var url = item.Select(Selectors.XPath(chapterRule.ChapterUrl))?.Value;
                            var sourceEntity = new MatchChapterSourceDto()
                            {
                                Id = Guid.NewGuid().ToString(),
                                ChapterName = name,
                                ChapterLink = url,
                                Keyword = name.SplitDefault(" ").JoinAsString(","),
                                SourceKey = settingProp.SourceKey,
                            };

                            sourceEntities.Add(sourceEntity);
                        }

                        await AddSourceAsync(context, sourceEntities); // 添加源

                        index++;
                    }
                    // 下一页的请求
                    var pageNodes = context.Selectable.SelectList(Selectors.XPath(chapterRule.Pager));
                    var nextKeywords = new string[] { "下", "下一页" };
                    foreach (var pageNode in pageNodes)
                    {
                        string text = pageNode.Value;
                        if (!text.IsNullOrEmpty() && nextKeywords.Any(n => text.Contains(n)))
                        {
                            var hasNext = pageNode.Links().FirstOrDefault();
                            BuildNextRequest(context, hasNext);
                        }
                    }
                }
                catch (Exception ex)
                {
                    logger.LogError(ex, ex.Message);
                    await spiderMgr.PushMessageAsync(bookProp.BookId, OperationTypeEnum.BookChapterSource, $"Parse Error:{ex.GetTraceMessage()}");
                }
            }

            /// <summary>
            /// 加入源
            /// </summary>
            /// <param name="context">上下文</param>
            /// <param name="sourceEntities">源</param>
            /// <returns></returns>
            public async Task AddSourceAsync(DataFlowContext context, List<MatchChapterSourceDto> sourceEntities)
            {
                CancellationToken cancellationToken = default;
                var uowMgr = context.ServiceProvider.GetRequiredService<IUnitOfWorkManager>();
                var chapterRepo = context.ServiceProvider.GetRequiredService<IBookCatalogueRepository>();
                var chapterSourceRepo = context.ServiceProvider.GetRequiredService<IBookCatalogueMatchSourceRepository>();
                var bookProp = (BookSourceDto)context.Request.Properties.GetOrDefault(QiDianConstant.BookChapterRequestProp);
                var settingProp = (BookSourceWarehouseDto)context.Request.Properties.GetOrDefault(QiDianConstant.BookChapterTargetTypeRequestProp);
                var logger = context.ServiceProvider.GetRequiredService<ILogger<MatchBookChapterSourceDataParser>>();

                using (var uow = uowMgr.Begin(requiresNew: true, isTransactional: true))
                {
                    var chapters = await chapterRepo.GetNotMatchListByBookIdAsync(bookProp.BookId, c => new MatchChapterSourceDto
                    {
                        Id = c.ID,
                        ChapterLink = c.ContentLink,
                        ChapterName = c.CatalogueName,
                        Keyword = c.Keyword
                    }, cancellationToken);

                    var modifyChapters = new List<MatchChapterSourceDto>();

                    foreach (var item in sourceEntities)
                    {
                        var keywords = item.Keyword.SplitDefault();
                        var currentChapter = chapters.Where(c =>
                        {
                            var chapterKeywords = c.Keyword.SplitDefault();
                            bool isMatch = c.ChapterName == item.ChapterName || !keywords.Intersect(chapterKeywords).IsNullOrEmpty() || keywords.Any(k => c.ChapterName.Contains(k));
                            return isMatch;
                        }).FirstOrDefault();

                        if (currentChapter != null)
                        {
                            chapters.Remove(currentChapter);
                            currentChapter.ChapterLink = item.ChapterLink;
                            modifyChapters.Add(currentChapter);
                        }
                    }

                    // 有就添加
                    if (modifyChapters.Any())
                    {
                        var chapterSources = await chapterSourceRepo.GetListByFilterAsync(new ChapterSourceFilterDto() { BookId = bookProp.BookId, SourceKey = bookProp.SourceKey }, bm => bm, cancellationToken); // 当前匹配源
                        try
                        {
                            var addSources = new List<BookCatalogueMatchSourceEntity>();

                            foreach (var source in modifyChapters)
                            {
                                var currentChapterSources = chapterSources.FirstOrDefault(c => c.BookCatalogueId == source.Id);
                                if (currentChapterSources == null)
                                {
                                    addSources.Add(new BookCatalogueMatchSourceEntity()
                                    {
                                        BookCatalogueId = source.Id,
                                        BookId = bookProp.BookId,
                                        SourceKey = bookProp.SourceKey,
                                        CreateDateTime = DateTime.Now,
                                        SourceUrl = source.ChapterLink,
                                    });
                                }
                            }

                            if (addSources.Any())
                            {
                                await chapterSourceRepo.BulkInsertAsync(addSources, cancellationToken);
                                await uow.CompleteAsync(cancellationToken);
                            }
                        }
                        catch (Exception ex)
                        {
                            logger.LogError($"更新章节书源失败，{ex.Message}", ex);
                            await uow.RollbackAsync();
                            throw;
                        }

                    }
                }
            }

            /// <summary>
            /// 构建下一次请求
            /// </summary>
            /// <param name="context">上下文</param>
            /// <param name="hasNext">下一页</param>
            public void BuildNextRequest(DataFlowContext context, string hasNext)
            {
                if (!hasNext.IsNullOrEmpty())
                {
                    var nextRequest = new Request(hasNext)
                    {
                        Timeout = 10000
                    };
                    if (!context.Request.Headers.UserAgent.IsNullOrEmpty())
                        nextRequest.Headers.UserAgent = context.Request.Headers.UserAgent;

                    if (!context.Request.Headers.Cookie.IsNullOrEmpty())
                        nextRequest.Headers.Cookie = context.Request.Headers.Cookie;

                    foreach (var item in context.Request.Properties)
                    {
                        nextRequest.Properties.Add(item.Key, item.Value);
                    }
                    context.AddFollowRequests(nextRequest);
                }
            }
        }
    }
}
